package ttf

import (
	"errors"
	"image"
	"image/color"
	"image/draw"
	"io/ioutil"

	"code.google.com/p/freetype-go/freetype/raster"
	"code.google.com/p/freetype-go/freetype/truetype"
)

const (
	nGlyphs     = 256
	nXFractions = 4
	nYFractions = 1
)

func pt(x, y int) raster.Point {
	return raster.Point{
		X: raster.Fix32(x << 8),
		Y: raster.Fix32(y << 8),
	}
}

type cacheEntry struct {
	valid  bool
	glyph  truetype.Index
	mask   *image.Alpha
	offset image.Point
}

type Font struct {
	Height    int
	size, dpi float64
	r         *raster.Rasterizer
	ttf       *truetype.Font
	src       image.Image
	glyphBuf  *truetype.GlyphBuf
	scale     int32
	cache     [nGlyphs * nXFractions * nYFractions]cacheEntry
	tabwidth  raster.Fix32
}

func NewFont(path string, size float64, col color.Color) (*Font, error) {
	f := &Font{
		// TODO magic 4
		//Height:   int(raster.Fix32(size*96.0*(256.0/72.0))>>8) - 4,
		Height:   int(raster.Fix32(size*96.0*(256.0/72.0))>>8),
		size:     size,
		dpi:      96,
		r:        raster.NewRasterizer(0, 0),
		src:      image.NewUniform(col),
		glyphBuf: truetype.NewGlyphBuf(),
		scale:    12 << 6,
	}

	bytes, err := ioutil.ReadFile(path)
	if err != nil {
		return nil, err
	}
	parsed, err := truetype.Parse(bytes)
	if err != nil {
		return nil, err
	}
	f.ttf = parsed
	f.recalc()
	f.tabwidth = f.tabpx(4) // TODO don't hardcode 4
	return f, nil
}

func (f *Font) Draw(img draw.Image, s []rune) []int {
	px := make([]int, 1, len(s)+1)
	px[0] = img.Bounds().Min.X

	// TODO magic 3?
	pt := pt(0, f.Height-3)
	prev, hasPrev := truetype.Index(0), false
	for _, rune := range s {
		// deal with tabstop specially
		if rune == '\t' {
			pt.X += f.tabwidth
			px = append(px, px[0]+int(pt.X>>8))
			continue
		}
		index := f.ttf.Index(rune)
		if hasPrev {
			pt.X += raster.Fix32(f.ttf.Kerning(f.scale, prev, index)) << 2
		}
		mask, offset, err := f.glyph(index, pt)
		if err != nil {
			panic(err)
		}
		pt.X += raster.Fix32(f.ttf.HMetric(f.scale, index).AdvanceWidth) << 2
		glyphRect := mask.Bounds().Add(offset)
		dr := img.Bounds().Intersect(glyphRect)
		if !dr.Empty() {
			mp := image.Point{0, dr.Min.Y - glyphRect.Min.Y}
			draw.DrawMask(img, dr, f.src, image.ZP, mask, mp, draw.Over)
		}
		prev, hasPrev = index, true
		px = append(px, px[0]+int(pt.X>>8))
	}
	return px
}

func (f *Font) tabpx(width int) raster.Fix32 {
	space := f.ttf.Index(' ')
	spacewidth := raster.Fix32(f.ttf.HMetric(f.scale, space).AdvanceWidth) << 2
	return raster.Fix32(width * int(spacewidth))
}

func (f *Font) recalc() {
	f.scale = int32(f.size * f.dpi * (64.0 / 72.0))
	if f.ttf == nil {
		f.r.SetBounds(0, 0)
	} else {
		b := f.ttf.Bounds(f.scale)
		xmin := +int(b.XMin) >> 6
		ymin := -int(b.YMax) >> 6
		xmax := +int(b.XMax+63) >> 6
		ymax := -int(b.YMin-63) >> 6
		f.r.SetBounds(xmax-xmin, ymax-ymin)
	}
	for i := range f.cache {
		f.cache[i] = cacheEntry{}
	}
}

func (f *Font) drawcontour(ps []truetype.Point, dx, dy raster.Fix32) {
	if len(ps) == 0 {
		return
	}
	start := raster.Point{
		X: dx + raster.Fix32(ps[0].X<<2),
		Y: dy - raster.Fix32(ps[0].Y<<2),
	}
	f.r.Start(start)
	q0, on0 := start, true
	for _, p := range ps[1:] {
		q := raster.Point{
			X: dx + raster.Fix32(p.X<<2),
			Y: dy - raster.Fix32(p.Y<<2),
		}
		on := p.Flags&0x01 != 0
		if on {
			if on0 {
				f.r.Add1(q)
			} else {
				f.r.Add2(q0, q)
			}
		} else {
			if on0 {
				// No-op.
			} else {
				mid := raster.Point{
					X: (q0.X + q.X) / 2,
					Y: (q0.Y + q.Y) / 2,
				}
				f.r.Add2(q0, mid)
			}
		}
		q0, on0 = q, on
	}
	if on0 {
		f.r.Add1(start)
	} else {
		f.r.Add2(q0, start)
	}
}

func (f *Font) rasterize(glyph truetype.Index, fx, fy raster.Fix32) (*image.Alpha, image.Point, error) {
	if err := f.glyphBuf.Load(f.ttf, f.scale, glyph, truetype.FullHinting); err != nil {
		return nil, image.ZP, err
	}
	xmin := int(fx+raster.Fix32(f.glyphBuf.B.XMin<<2)) >> 8
	ymin := int(fy-raster.Fix32(f.glyphBuf.B.YMax<<2)) >> 8
	xmax := int(fx+raster.Fix32(f.glyphBuf.B.XMax<<2)+0xff) >> 8
	ymax := int(fy-raster.Fix32(f.glyphBuf.B.YMin<<2)+0xff) >> 8
	if xmin > xmax || ymin > ymax {
		return nil, image.ZP, errors.New("freetype: negative sized glyph")
	}
	fx += raster.Fix32(-xmin << 8)
	fy += raster.Fix32(-ymin << 8)
	f.r.Clear()
	e0 := 0
	for _, e1 := range f.glyphBuf.End {
		f.drawcontour(f.glyphBuf.Point[e0:e1], fx, fy)
		e0 = e1
	}
	a := image.NewAlpha(image.Rect(0, 0, xmax-xmin, ymax-ymin))
	f.r.Rasterize(raster.NewAlphaSrcPainter(a))
	return a, image.Point{xmin, ymin}, nil
}

func (f *Font) glyph(glyph truetype.Index, p raster.Point) (*image.Alpha, image.Point, error) {
	ix, fx := int(p.X>>8), p.X&0xff
	iy, fy := int(p.Y>>8), p.Y&0xff
	// Calculate the index t into the cache array.
	tg := int(glyph) % nGlyphs
	tx := int(fx) / (256 / nXFractions)
	ty := int(fy) / (256 / nYFractions)
	t := ((tg*nXFractions)+tx)*nYFractions + ty
	if f.cache[t].valid && f.cache[t].glyph == glyph {
		return f.cache[t].mask, f.cache[t].offset.Add(image.Point{ix, iy}), nil
	}
	mask, offset, err := f.rasterize(glyph, fx, fy)
	if err != nil {
		return nil, image.ZP, err
	}
	f.cache[t] = cacheEntry{true, glyph, mask, offset}
	return mask, offset.Add(image.Point{ix, iy}), nil
}
